using System;
using System.IO;

using Exortech.NetReflector;

using ThoughtWorks.CruiseControl.Core;
using ThoughtWorks.CruiseControl.Core.Util;

namespace Trumpi.CruiseControl.Core.Publishers
{
	[ReflectorType("svnpublisher")]
	public class SubversionPublisher : ITask
	{
		private ProcessExecutor executor;
		private string username;
		private string password;
		private string executable = "svn.exe";
		private string sourceDirectory;
		private string commitUrl;

		public SubversionPublisher() : this(new ProcessExecutor())
		{
		}

		public SubversionPublisher(ProcessExecutor executor)
		{
			this.executor = executor;
		}

		#region ITask Members

		public void Run(IIntegrationResult result)
		{
			DirectoryInfo temporaryFolder = CreateTemporaryDirectory();
			try
			{
				ProcessInfo checkoutProcess = CheckoutProcessInfo(temporaryFolder, result);
				Execute(checkoutProcess);
				WriteFilesToTempFolder(temporaryFolder, result);
				CommitFiles(temporaryFolder, result);
				result.AddTaskResult("Success");
			}
			catch(Exception e)
			{
				throw new ThoughtWorks.CruiseControl.Core.Tasks.BuilderException(this, "Failed", e);
			}
			finally
			{
				//temporaryFolder.Delete(true);
			}
		}

		#endregion

		private void Execute(ProcessInfo info)
		{
			ProcessResult result = executor.Execute(info);
			if(result.ExitCode != 0)
			{
				throw new Exception("Exit code is " + result.ExitCode);
			}
			if(result.HasErrorOutput)
			{
				throw new Exception(result.StandardError);
			}
			if(result.TimedOut)
			{
				throw new Exception("Timed out");
			}
		}

		private void CommitFiles(DirectoryInfo tempFolder, IIntegrationResult result)
		{
			ProcessInfo commitProcessInfo = GetSvnProcessInfo("commit", ".", result, tempFolder.FullName);
			executor.Execute(commitProcessInfo);
		}

		private void WriteFilesToTempFolder(DirectoryInfo tempFolder, IIntegrationResult result)
		{
			DirectoryInfo sourceDirectory = new DirectoryInfo(SourceDirectory);

			foreach(FileInfo fileInfo in sourceDirectory.GetFiles())
			{
				FileInfo destinationFileInfo = new FileInfo(
					Path.Combine(tempFolder.FullName, fileInfo.Name));

				bool addFile = !destinationFileInfo.Exists;
				fileInfo.CopyTo(destinationFileInfo.FullName, true);

				if(addFile)
				{
					AddFileToSubversion(destinationFileInfo, result);
				}
			}

			foreach(FileInfo fileInfo in tempFolder.GetFiles())
			{
				FileInfo sourceFileInfo = new FileInfo(
					Path.Combine(sourceDirectory.FullName, fileInfo.Name));

				bool deleteFile = !sourceFileInfo.Exists;
				if(deleteFile)
				{
					DeleteFileFromSubversion(fileInfo, result);
				}
			}
		}

		private void AddFileToSubversion(FileInfo fileInfo, IIntegrationResult result)
		{
			ProcessInfo addFileProcessInfo = GetAddFileProcessInfo(fileInfo, result);
			executor.Execute(addFileProcessInfo);
		}

		private void DeleteFileFromSubversion(FileInfo fileInfo, IIntegrationResult result)
		{
			ProcessInfo deleteFileProcessInfo = GetDeleteFileProcessInfo(fileInfo, result);
			executor.Execute(deleteFileProcessInfo);
		}

		private bool Successful(ProcessResult result)
		{
			return !result.TimedOut && result.ExitCode == 0;
		}

		private ProcessInfo GetDeleteFileProcessInfo(FileInfo fileInfo, IIntegrationResult result)
		{
			return GetSvnProcessInfo("delete", ".", result, fileInfo.FullName);
		}

		private ProcessInfo GetAddFileProcessInfo(FileInfo fileInfo, IIntegrationResult result)
		{
			return GetSvnProcessInfo("add", ".", result, fileInfo.FullName);
		}

		private ProcessInfo CheckoutProcessInfo(DirectoryInfo checkoutDirectory, IIntegrationResult result)
		{
			return GetSvnProcessInfo("checkout", checkoutDirectory.FullName, result, CommitUrl);
		}

		private ProcessInfo GetSvnProcessInfo(string command, string workingPath, IIntegrationResult result, string url)
		{
			ProcessArgumentBuilder buffer = new ProcessArgumentBuilder();
			buffer.Append(command);
			buffer.AppendArgument(QuoteIfHasSpaces(url));
			if(command != "add" && command != "delete")
			{
				if(command != "commit")
				{
					buffer.AppendArgument(".");
				}
				buffer.AppendArgument("--username \"{0}\"", Username);
				buffer.AppendArgument("--password \"{0}\"", Password);
				if(command == "commit")
				{
					buffer.AppendArgument("--message AddBuild");
				}
				buffer.AppendArgument("--non-interactive");
			}
			return new ProcessInfo(Executable, buffer.ToString(), result.BaseFromWorkingDirectory(workingPath));
		}

		private DirectoryInfo CreateTemporaryDirectory()
		{
			string pathName = Path.GetTempFileName();
			File.Delete(pathName);
			return Directory.CreateDirectory(pathName);
		}

		private string QuoteIfHasSpaces(string s)
		{
			if(StringUtil.Contains(s, " "))
			{
				return "\"" + s + "\"";
			}
			else
			{
				return s;
			}
		}

		[ReflectorProperty("username", Required=false)]
		public string Username
		{
			get{ return username; }
			set{ username = value; }
		}

		[ReflectorProperty("password", Required=false)]
		public string Password
		{
			get { return password; }
			set { password = value; }
		}

		[ReflectorProperty("executable", Required=false)]
		public string Executable
		{
			get { return executable; }
			set { executable = value; }
		}

		[ReflectorProperty("sourceDir", Required=true)]
		public string SourceDirectory
		{
			get { return sourceDirectory; }
			set { sourceDirectory = value; }
		}

		[ReflectorProperty("publishUrl")]
		public string CommitUrl
		{
			get { return commitUrl; }
			set { commitUrl = value; }
		}
	}
}
